package com.jzo2o.market.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.market.dto.request.CouponUseBackReqDTO;
import com.jzo2o.api.market.dto.request.CouponUseReqDTO;
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
import com.jzo2o.api.market.dto.response.CouponUseResDTO;
import com.jzo2o.common.expcetions.BadRequestException;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.expcetions.DBException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.*;
import com.jzo2o.market.enums.CouponStatusEnum;
import com.jzo2o.market.mapper.CouponMapper;
import com.jzo2o.market.model.domain.Coupon;
import com.jzo2o.market.model.domain.CouponUseBack;
import com.jzo2o.market.model.domain.CouponWriteOff;
import com.jzo2o.market.model.dto.request.CouponOperationPageQueryReqDTO;
import com.jzo2o.market.model.dto.request.SeizeCouponReqDTO;
import com.jzo2o.market.model.dto.response.ActivityInfoResDTO;
import com.jzo2o.market.model.dto.response.CouponInfoResDTO;
import com.jzo2o.market.service.IActivityService;
import com.jzo2o.market.service.ICouponService;
import com.jzo2o.market.service.ICouponUseBackService;
import com.jzo2o.market.service.ICouponWriteOffService;
import com.jzo2o.market.utils.CouponUtils;
import com.jzo2o.mvc.utils.UserContext;
import com.jzo2o.mysql.utils.PageUtils;
import com.jzo2o.redis.utils.RedisSyncQueueUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import static com.jzo2o.common.constants.ErrorInfo.Code.SEIZE_COUPON_FAILD;
import static com.jzo2o.market.constants.RedisConstants.RedisKey.*;


/**
 * <p>
 * 服务实现类
 * </p>
 */
@Service
@Slf4j
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {

    @Resource(name = "seizeCouponScript")
    private DefaultRedisScript<String> seizeCouponScript;
    @Resource
    private RedisTemplate<Object, Object> redisTemplate;
    @Resource
    private IActivityService activityService;
    @Resource
    private ICouponUseBackService couponUseBackService;
    @Resource
    private ICouponWriteOffService couponWriteOffService;


    /**
     * 根据活动ID查询优惠券领取记录
     */
    @Override
    public PageResult<CouponInfoResDTO> queryForPageOfOperation(CouponOperationPageQueryReqDTO couponOperationPageQueryReqDTO) {
        // 1.数据校验
        if (ObjectUtils.isNull(couponOperationPageQueryReqDTO.getActivityId())) {
            throw new BadRequestException("请指定活动");
        }
        // 2.数据查询
        // 将分页查询参数转换为MyBatis-Plus的Page对象（指定查询的是Coupon表）
        Page<Coupon> couponQueryPage = PageUtils.parsePageQuery(couponOperationPageQueryReqDTO, Coupon.class);
        // 构建查询条件
        LambdaQueryWrapper<Coupon> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 按"活动ID"筛选（只查与请求中活动ID匹配的优惠券）
        lambdaQueryWrapper.eq(Coupon::getActivityId, couponOperationPageQueryReqDTO.getActivityId());

        // 执行分页查询（基于条件查询优惠券数据）
        Page<Coupon> couponPage = baseMapper.selectPage(couponQueryPage, lambdaQueryWrapper);
        // 将查询结果（Coupon实体）转换为响应DTO（CouponInfoResDTO）并返回分页结果
        return PageUtils.toPage(couponPage, CouponInfoResDTO.class);
    }

    /**
     * 作废指定活动未使用的优惠券
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void revoke(Long activityId) {
        lambdaUpdate()
                .set(Coupon::getStatus, CouponStatusEnum.VOIDED.getStatus()) // 设置优惠券状态为作废
                .eq(Coupon::getActivityId, activityId) // 按"活动ID"筛选（只查与请求中活动ID匹配的优惠券）
                .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus()) // 按"未使用"筛选（只查未使用的优惠券）
                .update();
    }

    /**
     * 根据活动ID查询领取优惠券数量
     */
    @Override
    public Integer countReceiveNumByActivityId(Long activityId) {
        return lambdaQuery().eq(Coupon::getActivityId, activityId) // 按"活动ID"筛选（只查与请求中活动ID匹配的优惠券）
                .count();
    }

    /**
     * 处理优惠券过期
     */
    @Override
    public void processExpireCoupon() {
        lambdaUpdate()
                .set(Coupon::getStatus, CouponStatusEnum.INVALID.getStatus()) // 优惠券状态设为"已失效"
                .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus()) // 按"未使用"筛选（只查未使用的优惠券）
                .le(Coupon::getValidityTime, DateUtils.now()) // 按"有效期"筛选（只查已过期的优惠券）
                .update();
    }


    /**
     * 我的优惠券列表
     */
    @SuppressWarnings("unchecked")
    @Override
    public List<CouponInfoResDTO> queryForList(Long lastId, Long userId, Integer status) {
        // 1. 校验状态合法性（仅允许1-3的状态值）
        if (status > 3 || status < 1) {
            throw new BadRequestException("请求状态不存在");
        }

        // 2. 构建查询条件
        LambdaQueryWrapper<Coupon> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 查询条件
        lambdaQueryWrapper.eq(Coupon::getStatus, status) // 按指定状态筛选
                .eq(Coupon::getUserId, userId) // 按用户ID筛选
                .lt(ObjectUtils.isNotNull(lastId), Coupon::getId, lastId); // 若有lastId，只查ID更小的数据（分页加载）
        // 仅查询ID字段（减少数据传输）
        lambdaQueryWrapper.select(Coupon::getId);
        // 按ID倒序排序（最新数据在前）
        lambdaQueryWrapper.orderByDesc(Coupon::getId);
        // 限制最多返回10条数据
        lambdaQueryWrapper.last(" limit 10 ");

        // 3. 先查询符合条件的优惠券ID列表
        List<Coupon> couponsOnlyId = baseMapper.selectList(lambdaQueryWrapper);
        // 若没有数据，返回空列表
        if (CollUtils.isEmpty(couponsOnlyId)) {
            return new ArrayList<>();
        }

        // 4. 根据ID批量查询完整优惠券信息并转换返回
        // 优惠id列表
        List<Long> ids = couponsOnlyId.stream()
                .map(Coupon::getId) // 获取优惠券ID
                .collect(Collectors.toList()); // 转为列表
        // 获取优惠券数据
        List<Coupon> coupons = baseMapper.selectBatchIds(ids);

        // 转换为响应DTO列表返回
        return BeanUtils.copyToList(coupons, CouponInfoResDTO.class);
    }

    /**
     * 抢券
     */
    @Override
    public void seizeCoupon(SeizeCouponReqDTO seizeCouponReqDTO) {
        // 1.校验活动开始时间或结束
        // 抢券时间
        ActivityInfoResDTO activity = activityService.getActivityInfoByIdFromCache(seizeCouponReqDTO.getId());
        LocalDateTime now = DateUtils.now();
        if (activity == null ||
                activity.getDistributeStartTime().isAfter(now)) {
            throw new CommonException(SEIZE_COUPON_FAILD, "活动未开始");
        }
        if (activity.getDistributeEndTime().isBefore(now)) {
            throw new CommonException(SEIZE_COUPON_FAILD, "活动已结束");
        }

        // 2.抢券准备
//         key: 抢券同步队列，资源库存,抢券列表
//         argv：抢券id,用户id
        int index = (int) (seizeCouponReqDTO.getId() % 10);
        // 同步队列redisKey
        String couponSeizeSyncRedisKey = RedisSyncQueueUtils.getQueueRedisKey(COUPON_SEIZE_SYNC_QUEUE_NAME, index);
        // 同步队列的field采用时间戳+用户id
        String syn_field = Instant.now().getEpochSecond() + "_" + UserContext.currentUserId();
        // 资源库存redisKey
        String resourceStockRedisKey = String.format(COUPON_RESOURCE_STOCK, index);
        // 抢券列表
        String couponSeizeListRedisKey = String.format(COUPON_SEIZE_LIST, activity.getId(), index);

        log.debug("seize coupon keys -> couponSeizeListRedisKey->{},resourceStockRedisKey->{},couponSeizeListRedisKey->{},seizeCouponReqDTO.getId()->{},UserContext.currentUserId():{}",
                couponSeizeListRedisKey, resourceStockRedisKey, couponSeizeListRedisKey, seizeCouponReqDTO.getId(), UserContext.currentUserId());
        // 3.抢券结果
        Object execute = redisTemplate.execute(seizeCouponScript, Arrays.asList(couponSeizeSyncRedisKey, resourceStockRedisKey, couponSeizeListRedisKey),
                seizeCouponReqDTO.getId(), UserContext.currentUserId(), syn_field);
        log.debug("seize coupon result : {}", execute);
        // 4.处理lua脚本结果
        if (execute == null) {
            throw new CommonException(SEIZE_COUPON_FAILD, "抢券失败");
        }
        long result = NumberUtils.parseLong(execute.toString());
        if (result > 0) {
            // 抢券成功
            return;
        }
        if (result == -1) {
            throw new CommonException(SEIZE_COUPON_FAILD, "限领一张");
        }
        if (result == -2 || result == -4) {
            throw new CommonException(SEIZE_COUPON_FAILD, "已抢光!");
        }
        throw new CommonException(SEIZE_COUPON_FAILD, "抢券失败");
    }


    /**
     * 获取可用优惠券
     */
    @Override
    public List<AvailableCouponsResDTO> getAvailable(BigDecimal totalAmount) {
        Long userId = UserContext.currentUserId();
        // 1.查询优惠券
        List<Coupon> coupons = lambdaQuery()
                .eq(Coupon::getUserId, userId)
                .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())
                .gt(Coupon::getValidityTime, DateUtils.now())
                .le(Coupon::getAmountCondition, totalAmount)
                .list();
        // 判空
        if (CollUtils.isEmpty(coupons)) {
            return new ArrayList<>();
        }

        // 2.组装数据计算优惠金额
        List<AvailableCouponsResDTO> collect = coupons.stream()
                // 计算优惠金额，.peek()方法用于在处理元素时执行一个操作，这里用于计算优惠金额
                .peek(coupon -> coupon.setDiscountAmount(CouponUtils.calDiscountAmount(coupon, totalAmount)))
                //过滤优惠金额大于0且小于订单金额的优惠券
                .filter(coupon -> coupon.getDiscountAmount().compareTo(new BigDecimal(0)) > 0 && coupon.getDiscountAmount().compareTo(totalAmount) < 0)
                // 类型转换
                .map(coupon -> BeanUtils.copyBean(coupon, AvailableCouponsResDTO.class))
                //按优惠金额降序排
                .sorted(Comparator.comparing(AvailableCouponsResDTO::getDiscountAmount).reversed())
                .collect(Collectors.toList());
        return collect;
    }


    /**
     * 优惠券核销
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CouponUseResDTO use(CouponUseReqDTO couponUseReqDTO) {
        //判空
        if (ObjectUtils.isNull(couponUseReqDTO.getOrdersId()) ||
                ObjectUtils.isNull(couponUseReqDTO.getTotalAmount()))
        {
            throw new BadRequestException("优惠券核销的订单信息为空");
        }
        //用户id
        Long userId = UserContext.currentUserId();
        //查询优惠券信息
        Coupon coupon = baseMapper.selectById(couponUseReqDTO.getId());
        // 优惠券判空
        if (coupon == null ) {
            throw new BadRequestException("优惠券不存在");
        }
        if ( ObjectUtils.notEqual(coupon.getUserId(),userId)) {
            throw new BadRequestException("只允许核销自己的优惠券");
        }
        //更新优惠券表的状态
        boolean update = lambdaUpdate()
                .eq(Coupon::getId, couponUseReqDTO.getId())
                .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())
                .gt(Coupon::getValidityTime, DateUtils.now())
                .le(Coupon::getAmountCondition, couponUseReqDTO.getTotalAmount())
                .set(Coupon::getOrdersId, couponUseReqDTO.getOrdersId())
                .set(Coupon::getStatus, CouponStatusEnum.USED.getStatus())
                .set(Coupon::getUseTime, DateUtils.now())
                .update();
        if (!update) {
            throw new DBException("优惠券核销失败");
        }

        //添加核销记录
        CouponWriteOff couponWriteOff = CouponWriteOff.builder()
                .id(IdUtils.getSnowflakeNextId())
                .couponId(couponUseReqDTO.getId())
                .userId(userId)
                .ordersId(couponUseReqDTO.getOrdersId())
                .activityId(coupon.getActivityId())
                .writeOffTime(DateUtils.now())
                .writeOffManName(coupon.getUserName())
                .writeOffManPhone(coupon.getUserPhone())
                .build();
        if(!couponWriteOffService.save(couponWriteOff)){
            throw new DBException("优惠券核销失败");
        }

        // 计算优惠金额
        BigDecimal discountAmount = CouponUtils.calDiscountAmount(coupon, couponUseReqDTO.getTotalAmount());
        CouponUseResDTO couponUseResDTO = new CouponUseResDTO();
        couponUseResDTO.setDiscountAmount(discountAmount);
        return couponUseResDTO;
    }


    /**
     * 优惠券退回
     */
    @Override
    public void useBack(CouponUseBackReqDTO couponUseBackReqDTO) {
        // 1.校验
        if (ObjectUtils.isNull(couponUseBackReqDTO.getId())) {
            throw new BadRequestException("优惠券id为空");
        }
        // 2.查询优惠券
        Coupon coupon = baseMapper.selectById(couponUseBackReqDTO.getId());
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }
        // 3.校验优惠券状态
        if (ObjectUtils.notEqual(coupon.getStatus(), CouponStatusEnum.USED.getStatus())) {
            throw new BadRequestException("优惠券状态不正确");
        }
        // 4.更新优惠券状态
        boolean update = lambdaUpdate()
                .eq(Coupon::getId, couponUseBackReqDTO.getId())
                .eq(Coupon::getStatus, CouponStatusEnum.USED.getStatus())
                .set(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())
                .set(Coupon::getOrdersId, null)
                .set(Coupon::getUseTime, null)
                .update();
        if (!update) {
            throw new DBException("优惠券退回失败");
        }
        // 5.添加退回记录
        CouponUseBack couponUseBack = new CouponUseBack();
        couponUseBack.setId(IdUtils.getSnowflakeNextId());
        couponUseBack.setCouponId(couponUseBackReqDTO.getId());
        couponUseBack.setUserId(coupon.getUserId());
        couponUseBack.setUseBackTime(DateUtils.now());
        couponUseBack.setWriteOffTime(coupon.getUseTime());
        //6. 删除writeOff记录
        boolean remove = couponWriteOffService.lambdaUpdate()
                .eq(CouponWriteOff::getCouponId, couponUseBackReqDTO.getId())
                .remove();
        if (!remove) {
            throw new DBException("核销记录删除失败");
        }
        if (!couponUseBackService.save(couponUseBack)) {
            throw new DBException("优惠券退回失败");
        }
    }

    /**
     * 获取优惠券id
     */
    @Override
    public Long getCouponId(CouponUseReqDTO couponUseReqDTO) {
        //使用订单id查询优惠券id
        Coupon coupon = lambdaQuery()
                .eq(Coupon::getOrdersId, couponUseReqDTO.getOrdersId())
                .one();
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }
        return coupon.getId();
    }


}
